iT邦幫忙

2024 iThome 鐵人賽

DAY 22
0

我們今天要來接續昨天實作的帳務報表頁面,進行進一步優化,主要是優化分類支出的呈現,讓使用者能快速掌握消費狀況。在現有的圓餅圖基礎上,新增顯示每個分類支出比例的區塊,並且對頁面下方的物品清單進行改進,讓使用者可以看到每個分類的總金額。

目標

  • 顯示分類比例區塊:在圓餅圖下方新增一個區塊,顯示每個分類佔總支出的比例,讓使用者快速了解不同分類的支出比例。
  • 優化物品清單:改變下方的物品清單顯示,改為按分類顯示每個分類的總金額,並按金額大小排序。
  • 支持頁面滑動顯示:當分類數超過6個時,支持水平滑動,並提供頁面指示器來標示當前頁數和總頁數。

https://ooorito.com/wp-content/uploads/2024/09/%E7%9B%AE%E6%A8%99%E7%A4%BA%E6%84%8F%E5%9C%96.webp

主要實作

更新 ReportViewModel

因為要實作分類比例區塊和優化物品清單,所以需要來更新 ViewModel。先宣告兩個 @Published 的變數:categoryPercentages 和 categoryTotals。

@Published var categoryPercentages: [(category: ItemCategory, percentage: Double)] = []
@Published var categoryTotals: [(category: ItemCategory, total: Double)] = []
  • categoryPercentages:用來顯示比例的資料。
  • categoryTotals:用來顯示物品清單的資料。

接下來讓這兩個變數擁有資料:

func calculateCategoryPercentages(items: [Item]) {
    let totalAmount = items.reduce(0) { $0 + $1.price }
    if totalAmount > 0 {
        
        var categoryAmounts: [ItemCategory: Double] = [:]
        
        // 計算每個分類的總金額
        for item in items {
            let category = item.category
            categoryAmounts[category, default: 0] += item.price
        }
        
        // 轉換為 (分類, 總金額) 的形式
        categoryTotals = categoryAmounts.map { (category, amount) in
            (category, amount)
        }.sorted { $0.1 > $1.1 }
        
        // 轉換為 (分類, 百分比) 的形式
        categoryPercentages = categoryAmounts.map { (category, amount) in
            (category, (amount / totalAmount) * 100)
        }.sorted { $0.1 > $1.1 }  // 依百分比排序
    }
}

別忘了在取得資料後就呼叫它:在 fetchData() 函數中呼叫 calculateCategoryPercentages。

func fetchData() {
    ...略
    calculateCategoryPercentages(items: items)
}

實作 CategoryPercentageView

有了資料來源,就可以實作比例區塊了!建立 CategoryPercentageView 並繼承 View。

struct CategoryPercentageView: View {
    var categoriesWithPercentages: [(ItemCategory, Double)]
    @State private var currentPage = 0
    
    var body: some View {
    
    }
}

CategoryPercentageView 顯示每個分類的百分比,一次最顯示 6 個分類,超過 6 個分類時,可以左右滑動來查看其餘分類,下方有點點來提示目前頁數。所以我們要使用 TabView 來完成可以左右滑動的分頁效果。

TabView

TabView 是 SwiftUI 中用來顯示多個畫面的一個容器,支持用戶在不同的畫面之間通過手勢滑動或點擊切換。常見的使用場景包括在 App 中實現底部的選單欄或頁面之間的切換。TabView 可以透過 PageTabViewStyle 來啟用分頁效果,讓使用者能夠水平滑動不同頁面。此外,它還可以使用自定義的頁面指示器來顯示目前頁數,提供靈活的視覺效果和互動體驗。

參考資料:

了解 TabView 的用法後,先把 TabView 加入到程式碼中:

VStack {
    TabView(selection: $currentPage) {
    
    }
    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))  // 隱藏內建的頁面指示器
    .frame(height: 160)  // 固定高度,支持滑動
    .overlay(
        PageControl(currentPage: $currentPage, numberOfPages: (categoriesWithPercentages.count + 5) / 6)
            .padding(.top, 180)  // 放置自訂頁面指示器,不覆蓋分類
    )
    .onChange(of: currentPage) { _ in
        print("Current Page: \(currentPage)")
    }
}
.frame(height: 200)  // 確保整體高度足夠顯示3行內容
.background(
    RoundedRectangle(cornerRadius: 10)
        .stroke(Color.gray, lineWidth: 1)
)

struct PageControl: View {
    @Binding var currentPage: Int
    var numberOfPages: Int
    
    var body: some View {
        HStack(spacing: 8) {
            ForEach(0..<numberOfPages, id: \.self) { page in
                Circle()
                    .fill(page == currentPage ? Color.black : Color.gray)
                    .frame(width: 8, height: 8)
            }
        }
    }
}

這時候就可以把我們要顯示的分類比例加進來:

ForEach(0..<(categoriesWithPercentages.count + 5) / 6) { pageIndex in
    LazyVGrid(columns: columns, spacing: 12) {
        ForEach(0..<6, id: \.self) { index in
            if pageIndex * 6 + index < categoriesWithPercentages.count {
                let (category, percentage) = categoriesWithPercentages[pageIndex * 6 + index]
                HStack {
                    Circle()
                        .fill(Color(hexString: category.categoryGroup.colorHex) ?? .black)
                        .frame(width: 20, height: 20)
                    Text(category.name)
                        .font(.body)
                        .lineLimit(1)
                    Text(String(format: "%.1f%%", percentage))
                        .font(.body)
                }
                .padding(2)
            } else {
                HStack {
                    Circle()
                        .fill(.clear)
                        .frame(width: 20, height: 20)
                    Text("")
                        .font(.caption)
                        .lineLimit(1)
                    Text("")
                        .font(.caption)
                }
                .padding(2)
            }
        }
    }
    .padding()
    .tag(pageIndex)
}

這樣 CategoryPercentageView 的部分就完成了~

更新 ReportView

可以將剛剛完成的 CategoryPercentageView 加入到 ReportView 之中:

CategoryPercentageView(categoriesWithPercentages: viewModel.categoryPercentages)
    .padding()

接著呢,我們來優化下方的列表。現況是顯示物品的列表,我們來把它改成顯示分類的總金額。

List {
    ForEach(viewModel.categoryTotals, id: \.category.id) { category, total in
        HStack {
            Image(systemName: category.iconName)
                .resizable()
                .frame(width: 24, height: 24)
                .foregroundColor(Color(hexString: category.categoryGroup.colorHex))
            
            Text(category.name)
                .font(.headline)
            
            Spacer()
            
            Text(String(format: "%.0f", total))
                .font(.subheadline)
                .foregroundColor(.gray)
        }
    }
}
.listStyle(InsetGroupedListStyle())

更新 MenuContent

別忘了把剛剛寫完的 ReportView 與側邊欄整合起來唷!

NavigationLink(destination: ReportView()) {
    MenuButton(title: "帳務報表", icon: "chart.pie")
}
.padding(8)

總結

這次我們優化帳務報表頁面,不僅在圓餅圖下方新增了分類比例,還優化了物品清單,讓使用者能更直覺地了解每個分類的總金額。明天我們將繼續優化帳務報表頁面,今天就先寫到這邊,我們明天見!


上一篇
Day 21: SwiftUI 帳務報表 - 圓餅圖
下一篇
Day 23: 掃描發票 QRCode 與取得內容
系列文
用 SwiftUI 掌控家庭日用品庫存30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言